package gloader

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/nats-io/nats.go"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"strings"
	"sync"
	"wzgames/internal/app"
	"wzgames/internal/constant"
	"wzgames/internal/model"
	"wzgames/internal/model/mcomm"
	"wzgames/pkg/common/component"
	"wzgames/pkg/components/grabbitmq"
	"wzgames/pkg/components/mysql"
	"wzgames/pkg/components/redis"
	"wzgames/pkg/config"
	"wzgames/pkg/logger"
	"wzgames/pkg/remote/etcd"
	"wzgames/pkg/utils/calcutil"
	"wzgames/pkg/utils/stringutil"
)

type CommManager struct {
	mu sync.RWMutex

	sysConf *model.GSys // 系统配置
	dbpxy   *DBProxy    // 数据库实例管理
	// redis
	rc    *component.ComponentConfig
	rpool *redis.RedisClient
	natsC *nats.Conn // nats
	// MQ
	mqc *component.ComponentConfig
	mq  *grabbitmq.MQ
	// 系统货币
	needCurrencies bool
	currencies     *mcomm.SysCurrencies

	needMts   bool
	maintains map[int64]*mcomm.MainT

	stopCh chan struct{}
	evtch  chan *etcd.KvEvt
	// etcd路径
	sysDir  string
	currDir string
	mtDir   string
}

func newCommManager() *CommManager {
	r := &CommManager{
		evtch:     make(chan *etcd.KvEvt, 8),
		stopCh:    make(chan struct{}, 1),
		dbpxy:     newDBProxy(),
		sysDir:    fmt.Sprintf("%s/sys", constant.CcCommRoot),
		currDir:   fmt.Sprintf("%s/currency", constant.CcCommRoot),
		mtDir:     fmt.Sprintf("%s/mt", constant.CcCommRoot),
		maintains: make(map[int64]*mcomm.MainT),
	}

	return r
}

// LoadSysConfig 加载系统基础配置
func (cm *CommManager) LoadSysConfig() error {
	b, err := CClient().GetRawValue(cm.sysDir)
	if err != nil {
		logger.Error("[GLoader] load sys config fail", zap.Error(err))
		return err
	}

	return cm.parseSysConfig(b)
}

func (cm *CommManager) parseSysConfig(b []byte) error {
	conf := &model.GSys{}
	err := json.Unmarshal(b, conf)
	if err != nil {
		logger.Error("[GLoader] load sys config fail on json.Unmarshal", zap.Error(err))
		return err
	}
	cm.mu.Lock()
	cm.sysConf = conf
	cm.mu.Unlock()
	return nil
}

// LoadSysCurrencies 加载系统货币
func (cm *CommManager) LoadSysCurrencies(need bool) error {
	cm.needCurrencies = true
	b, err := CClient().GetRawValue(cm.currDir)
	if err != nil {
		logger.Error("[GLoader] load sys currencies fail", zap.Error(err))
		return err
	}

	return cm.parseSysCurrencies(b)
}

func (cm *CommManager) parseSysCurrencies(b []byte) error {
	conf := &mcomm.SysCurrencies{}
	err := proto.Unmarshal(b, conf)
	if err != nil {
		logger.Error("[GLoader] load sys currencies fail on proto.Unmarshal", zap.Error(err))
		return err
	}
	cm.mu.Lock()
	cm.currencies = conf
	cm.mu.Unlock()
	return nil
}

func (cm *CommManager) LoadSysMaintain(need bool) error {
	cm.needMts = true
	err := CClient().GetRawValues(cm.mtDir, func(s string, data []byte) error {
		if s == cm.mtDir || data == nil {
			return nil
		}
		cm.parseSysMaintain(data)
		return nil
	})
	if err != nil {
		logger.Error("[GLoader] load sys maintain fail", zap.Error(err))
		return err
	}

	return nil
}

func (cm *CommManager) parseSysMaintain(b []byte) {
	mt := &mcomm.MainT{}
	err := proto.Unmarshal(b, mt)
	if err != nil {
		logger.Error("[GLoader] load sys maintain fail on proto.Unmarshal", zap.Error(err))
		return
	}
	cm.mu.Lock()
	if mt.Status != mcomm.Status_Up {
		delete(cm.maintains, mt.MtID)
	} else {
		cm.maintains[mt.MtID] = mt
	}
	cm.mu.Unlock()
}

func (cm *CommManager) removeSysMaintain(key string) {
	arr := strings.Split(key, "-")
	if len(arr) < 2 {
		return
	}
	mtId, _ := stringutil.ToInt64(arr[1], 0)
	if mtId > 0 {
		cm.mu.Lock()
		cm.mu.Unlock()
	}
}

// WatchCommConfig 监听基础配置变更
func (cm *CommManager) WatchCommConfig() error {
	go cm.onData()

	err := CClient().Watch(context.Background(), constant.CcCommRoot, true, cm.evtch)
	if err != nil {
		logger.Error("[GLoader] watch node /wzgames_base/common", zap.Error(err))
		return err
	}

	return nil
}

// onData 处理ETCD监听的数据变更
func (cm *CommManager) onData() {
	for {
		select {
		case e := <-cm.evtch:
			switch e.Type {
			case etcd.KvEventNew, etcd.KvEventUpdate:
				if strings.HasPrefix(e.Key, cm.sysDir) {
					err := cm.parseSysConfig(e.Value)
					if err == nil {
						cm.updateSysConfig()
					}
				} else if strings.HasPrefix(e.Key, cm.currDir) && cm.needCurrencies {
					_ = cm.parseSysCurrencies(e.Value)
				} else if strings.HasPrefix(e.Key, cm.mtDir) && cm.needMts {
					cm.parseSysMaintain(e.Value)
				}
			case etcd.KvEventDelete:
				if strings.HasPrefix(e.Key, cm.mtDir) && cm.needMts {
					k := strings.Replace(e.Key, fmt.Sprintf("%s/", cm.mtDir), "", 1)
					cm.removeSysMaintain(k)
				}
			}
		case <-cm.stopCh:
			return
		}
	}
}

func (cm *CommManager) updateSysConfig() {
	cm.updateRedis()
	cm.dbpxy.UpdateWdb(cm.sysConf.WdbC)
	cm.updateNats()
	cm.updateMqc()
}

// InitRedis redis初始化
func (cm *CommManager) InitRedis() error {
	cm.mu.Lock()
	defer cm.mu.Unlock()
	if cm.sysConf == nil || len(cm.sysConf.RedisC.ServerConfigs) == 0 {
		return fmt.Errorf("invalid redis config")
	}

	cm.rc = &component.ComponentConfig{
		ID:     redis.ID(),
		Name:   "redis-sys",
		Config: cm.rConfig(),
	}
	inst, err := app.Comp().CreateInstance(cm.rc)
	if err != nil {
		logger.Error("[GLoader] create sys redis pool fail", zap.String("Name", cm.rc.Name), zap.Error(err))
		return err
	}

	cm.rpool = inst.(*redis.RedisClient)
	return nil
}

func (cm *CommManager) rConfig() config.ConfData {
	c := &config.YAMLConfData{}
	c.ReplaceData(make(map[string]interface{}))
	rc := cm.sysConf.RedisC.ServerConfigs[0]
	c.SetRawValue("scheme", rc.Scheme)
	c.SetRawValue("addrs", rc.Server)
	c.SetRawValue("db", rc.DB)
	c.SetRawValue("username", rc.Username)
	c.SetRawValue("password", rc.Password)
	c.SetRawValue("maxRetries", rc.MaxRetries)
	c.SetRawValue("dialTimeout", rc.DialTimeout)
	c.SetRawValue("readTimeout", rc.ReadTimeout)
	c.SetRawValue("writeTimeout", rc.WriteTimeout)
	c.SetRawValue("minIdleConns", rc.MinIdleConns)
	c.SetRawValue("maxIdleConns", rc.MaxIdleConns)
	c.SetRawValue("connMaxIdleTime", rc.ConnMaxIdleTime)

	return c
}

func (cm *CommManager) updateRedis() {
	cm.mu.Lock()
	defer cm.mu.Unlock()
	if cm.rpool == nil {
		return
	}

	cm.rc.Update(cm.rConfig())
	newInst, err := app.Comp().UpdateInstance(cm.rc.Name, cm.rc)
	if err != nil {
		logger.Error("[GLoader] update sys redis pool fail", zap.String("Name", cm.rc.Name), zap.Error(err))
		return
	}
	cm.rpool = newInst.(*redis.RedisClient)
}

func (cm *CommManager) RedisPool() *redis.RedisClient {
	return cm.rpool
}

// InitNats Nats初始化
func (cm *CommManager) InitNats() error {
	cm.mu.Lock()
	defer cm.mu.Unlock()
	if cm.sysConf == nil || cm.sysConf.NatsC == nil {
		return fmt.Errorf("invalid nats config")
	}

	nc, err := nats.Connect(cm.sysConf.NatsC.Server)
	if err != nil {
		logger.Error("[GLoader] init nats fail", zap.Error(err))
		return err
	}
	cm.natsC = nc
	return nil
}

func (cm *CommManager) CreateNats() (*nats.Conn, error) {
	cm.mu.Lock()
	defer cm.mu.Unlock()
	if cm.sysConf == nil || cm.sysConf.NatsC == nil {
		return nil, fmt.Errorf("invalid nats config")
	}

	nc, err := nats.Connect(cm.sysConf.NatsC.Server)
	if err != nil {
		logger.Error("[GLoader] create nats conn fail", zap.Error(err))
		return nil, err
	}
	return nc, nil
}

func (cm *CommManager) updateNats() {
	cm.mu.Lock()
	defer cm.mu.Unlock()
	if cm.natsC == nil {
		return
	}
}

func (cm *CommManager) NatsC() *nats.Conn {
	return cm.natsC
}

// InitWdb 数据库管理初始化
func (cm *CommManager) InitWdb() error {
	if cm.sysConf == nil || cm.sysConf.WdbC == nil {
		return fmt.Errorf("invalid wdb config")
	}

	return cm.dbpxy.InitWdb(cm.sysConf.WdbC)
}

func (cm *CommManager) GetDBProxy(name string) *mysql.DS {
	return cm.dbpxy.GetProxy(name)
}

// InitMQ MQ初始化
func (cm *CommManager) InitMQ() error {
	cm.mu.Lock()
	defer cm.mu.Unlock()
	if cm.sysConf == nil || cm.sysConf.MqC == nil {
		return fmt.Errorf("invalid mq config")
	}

	cm.mqc = &component.ComponentConfig{
		ID:     grabbitmq.ID(),
		Name:   "mq-sys",
		Config: cm.mqConfig(),
	}
	inst, err := app.Comp().CreateInstance(cm.mqc)
	if err != nil {
		logger.Error("[GLoader] create sys mq fail", zap.String("Name", cm.mqc.Name), zap.Error(err))
		return err
	}

	cm.mq = inst.(*grabbitmq.MQ)
	return nil
}

func (cm *CommManager) mqConfig() config.ConfData {
	c := &config.YAMLConfData{}
	c.ReplaceData(make(map[string]interface{}))
	for _, mq := range cm.sysConf.MqC.BrokerConfigs {
		var broker strings.Builder // amqp://guest:guest@localhost:5672/
		broker.WriteString("amqp://")
		broker.WriteString(mq.User)
		broker.WriteString(":")
		broker.WriteString(mq.Password)
		broker.WriteString("@")
		broker.WriteString(mq.Host)
		broker.WriteString(":")
		broker.WriteString(mq.Port)
		broker.WriteString(mq.VirtualHost)
		c.SetRawValue(mq.Name, broker.String())
	}
	return c
}

func (cm *CommManager) updateMqc() {
	cm.mu.Lock()
	defer cm.mu.Unlock()
	if cm.mqc == nil {
		return
	}

	cm.mqc.Update(cm.mqConfig())
	newInst, err := app.Comp().UpdateInstance(cm.mqc.Name, cm.mqc)
	if err != nil {
		logger.Error("[GLoader] update sys mq inst fail", zap.String("Name", cm.mqc.Name), zap.Error(err))
		return
	}
	cm.mq = newInst.(*grabbitmq.MQ)
}

func (cm *CommManager) MQ() *grabbitmq.MQ {
	return cm.mq
}

// StartIdWorker 启动基于数据库的ID生成
func (cm *CommManager) StartIdWorker() error {
	return cm.dbpxy.StartIdWorker()
}

// GenID 基于数据库的ID生成
func (cm *CommManager) GenID(bizid int64, step uint64) uint64 {
	return cm.dbpxy.GenID(bizid, step)
}

func (cm *CommManager) Destroy() {
	if cm.natsC != nil {
		cm.natsC.Close()
	}
	cm.stopCh <- struct{}{}
	CClient().UnWatch(constant.CcCommRoot)
}

func (cm *CommManager) GetCurrency(curr string) *mcomm.SysCurrency {
	cm.mu.RLock()
	v, ok := cm.currencies.DataList[curr]
	cm.mu.RUnlock()
	if ok && v.Status == mcomm.Status_Up {
		return v
	}

	return nil
}

func (cm *CommManager) GetCurrencyByID(currId int32) *mcomm.SysCurrency {
	cm.mu.RLock()
	curr, ok := cm.currencies.CurrIdMap[currId]
	if ok {
		v, ok1 := cm.currencies.DataList[curr]
		cm.mu.RUnlock()
		if ok1 && v.Status == mcomm.Status_Up {
			return v
		}
		return nil
	}
	cm.mu.RUnlock()

	return nil
}

func (cm *CommManager) GetCurrencyName(currId int32) string {
	cm.mu.RLock()
	curr, ok := cm.currencies.CurrIdMap[currId]
	cm.mu.RUnlock()
	if ok {
		return curr
	}

	return ""
}

// CheckAmountPointByCurrency 根据币种检查金额的精度是否合法
func (cm *CommManager) CheckAmountPointByCurrency(curr, amount string) bool {
	sysCurr := cm.GetCurrency(curr)
	if sysCurr == nil {
		return false
	}

	return calcutil.CheckAmountDecimalPoint(amount, sysCurr.Precision)
}

func (cm *CommManager) GetCurrencyPrecision(currId int32) int32 {
	sc := cm.GetCurrencyByID(currId)
	if sc == nil {
		return 0
	}

	return sc.Precision
}
